package com.itcc.service;

import com.itcc.config.AppDownConfig;
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.HttpClientBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.*;
import java.net.URL;
import java.nio.file.*;
import java.util.concurrent.*;

@Service
public class FileDownloadService {

    @Autowired
    private AppDownConfig appConfigConfig;

    /**
     * 通过URL下载文件
     * @param fileUrl 文件的URL地址
     * @return 下载后的文件的公开访问URL
     * @throws Exception 如果下载过程中发生错误
     */
// https://cn.bing.com/images/search?view=detailV2&ccid=mH9YLFEL&id=C246DB78D32DD5C25CAEB3E8F5624312D6F6400B&thid=OIP.mH9YLFEL5YdVxJM82mjVJQHaEo&mediaurl=https%3a%2f%2fts1.tc.mm.bing.net%2fth%2fid%2fR-C.987f582c510be58755c4933cda68d525%3frik%3dC0D21hJDYvXosw%26riu%3dhttp%253a%252f%252fimg.pconline.com.cn%252fimages%252fupload%252fupc%252ftx%252fwallpaper%252f1305%252f16%252fc4%252f20990657_1368686545122.jpg%26ehk%3dnetN2qzcCVS4ALUQfDOwxAwFcy41oxC%252b0xTFvOYy5ds%253d%26risl%3d%26pid%3dImgRaw%26r%3d0&exph=1600&expw=2560&q=%e6%b8%85%e6%96%b0%e5%94%af%e7%be%8e%e9%a3%8e%e6%99%af%e5%a3%81%e7%ba%b8%e5%9b%be%e9%9b%86_%e6%b5%b7%e8%be%b9%e5%a3%81%e7%ba%b8%e9%ab%98%e6%b8%85+-+%e9%9a%8f%e6%84%8f%e4%bc%98%e6%83%a0%e5%88%b8&mode=overlay&FORM=IQFRBA&ck=3A193BB99FCDC360CC697492270F0CD3&selectedIndex=0&ajaxhist=0&ajaxserp=0
    public String downloadFile(String fileUrl) throws Exception {
        // 获取下载目录的路径
        Path downloadDir = Paths.get(appConfigConfig.getDownloadDir());

        // 如果下载目录不存在，则创建它
        if (!Files.exists(downloadDir)) {
            Files.createDirectories(downloadDir);
        }

        // 提取原始文件路径部分
        String fileName = new URL(fileUrl).getPath();
        // 截取斜杠后的内容，得到不带路径的文件名
        fileName = fileName.substring(fileName.lastIndexOf('/') + 1);

        //拼接下载目录路径，生成布标文件的完整路径以便文件下载和储存
        Path targetFile = downloadDir.resolve(fileName);

        // 如果已存在，直接返回
        if (Files.exists(targetFile)) {
            System.out.println("\033[32;4m" + "文件已存在，无需下载" + "\033[0m");
            return getPublicUrl(fileName);
        }

        // 获取远程文件大小
        long fileSize = getFileSize(fileUrl);

        // 设置线程数量，取4和可用处理器数量的较小值，以避免创建过多的线程
        int threadCount = Math.min(4, Runtime.getRuntime().availableProcessors());
        // 计算每个线程处理的数据块大小，将总文件大小除以线程数量
        long chunkSize = fileSize / threadCount;

        // 创建固定大小的线程池
        ExecutorService executor = Executors.newFixedThreadPool(threadCount);

        // 初始化一个Future数组，用于存储线程的执行结果
        Future<?>[] futures = new Future[threadCount];

        // 创建并提交下载任务
        for (int i = 0; i < threadCount; i++) {
            long startByte = i * chunkSize;
            long endByte = (i == threadCount - 1) ? fileSize - 1 : (startByte + chunkSize - 1);
            futures[i] = executor.submit(new DownloadTask(fileUrl, targetFile, startByte, endByte));
        }

        // 等待所有线程完成
        for (Future<?> future : futures) {
            future.get();
        }

        // 关闭线程池
        executor.shutdown();
        executor.shutdownNow();

        // 返回下载后的文件的公开访问URL
        return getPublicUrl(fileName);
    }

    /**
     * 获取文件的大小
     * 通过HTTP请求获取指定文件URL的文件大小
     *
     * @param fileUrl 文件的URL地址
     * @return 文件的大小，以字节为单位
     * @throws IOException 如果网络请求失败或读取文件信息时发生错误
     */
    private long getFileSize(String fileUrl) throws IOException {
        // 创建HTTP客户端，用于发送请求
        try (CloseableHttpClient client = HttpClientBuilder.create().build()) {
            // 创建HTTP GET请求，目标是获取文件URL的文件大小信息
            HttpGet request = new HttpGet(fileUrl);
            // 执行HTTP请求并获取响应
            try (CloseableHttpResponse response = client.execute(request)) {
                // 从响应实体中获取文件大小
                return response.getEntity().getContentLength();
            }
        }
    }

    //获取公开访问的URL
    private String getPublicUrl(String fileName) {
        return "http://localhost:8088/download/" + fileName;
    }

    /**
     * 下载任务类，实现 Callable 接口
     */
    static class DownloadTask implements Callable<Void> {

        private final String fileUrl;
        private final Path targetFile;
        private final long startByte;
        private final long endByte;

        /**
         * 构造下载任务
         *
         * @param fileUrl 文件的URL地址
         * @param targetFile 目标文件的路径
         * @param startByte 开始下载的字节位置
         * @param endByte 结束下载的字节位置
         */
        public DownloadTask(String fileUrl, Path targetFile, long startByte, long endByte) {
            this.fileUrl = fileUrl;
            this.targetFile = targetFile;
            this.startByte = startByte;
            this.endByte = endByte;
        }

        /**
         * 实现Callable接口的call方法，用于执行并发任务下载文件的一部分
         * 该方法使用HttpClient发起HTTP GET请求，利用Range头指定下载的字节范围，以实现文件的部分下载
         * 它将下载的文件部分写入到一个RandomAccessFile对象中，该对象代表了目标文件
         *
         * @return Void 返回null，表示该方法不返回任何结果
         * @throws Exception 如果下载过程中发生错误，将抛出Exception
         */
        public Void call() throws Exception {
            // 创建HttpClient实例，用于发送HTTP请求
            try (CloseableHttpClient client = HttpClientBuilder.create().build()) {
                // 创建HttpGet请求，并设置请求的URL为目标文件的URL
                HttpGet request = new HttpGet(fileUrl);
                // 设置请求头，指定下载的字节范围
                request.setHeader("Range", "bytes=" + startByte + "-" + endByte);

                // 发送HTTP请求，并获取响应
                try (CloseableHttpResponse response = client.execute(request)) {
                    // 从响应中获取输入流和RandomAccessFile对象，用于读取和写入文件
                    try (InputStream inputStream = response.getEntity().getContent();
                         RandomAccessFile raf = new RandomAccessFile(targetFile.toFile(), "rw")) {

                        // 将RandomAccessFile对象的写入位置移动到指定的起始字节
                        raf.seek(startByte);

                        // 创建缓冲区，用于读取输入流中的数据
                        byte[] buffer = new byte[8192];
                        int bytesRead;
                        // 循环读取输入流中的数据，并写入到RandomAccessFile对象中
                        while ((bytesRead = inputStream.read(buffer)) != -1) {
                            raf.write(buffer, 0, bytesRead);
                        }
                    }
                }
            }
            // 返回null，表示该方法不返回任何结果
            return null;
        }

    }
}
